#ifndef	__mesh_utils_H__
#define	__mesh_utils_H__

#include "rendPlatform.hpp"
#include "rendVect.hpp"
#include "rendBox.hpp"
#include "rendClipper.hpp"
#include "rendVectorArray.hpp"
#include "rendAttrBuffer.hpp"

#include <float.h>
#include <memory.h>

namespace rend
{

template < unsigned VECTDIM_T >
inline void genBBox3(
	const VectorArrayImm< float, VECTDIM_T >& verts,
	Box3& bbox)
{
	assert(VECTDIM_T >= 3);

	if (verts.count() == 0)
		return;

	bbox.setExtents(
		vect3::castU(verts.get(0)),
		vect3::castU(verts.get(0)));

	for (unsigned i = 1; i < verts.count(); ++i)
		bbox.merge(vect3::castU(verts.get(i)));
}


inline bool copyAttrPNTransform(
	Box3& bbox,
	VectorArray< float, 3 >& pos,
	VectorArray< float, 3 >& nrm,
	const VectorArrayImm< float, 3 >& src_pos,
	const VectorArrayImm< float, 3 >& src_nrm,
	const matx4& mat)
{
	assert(src_pos.count());
	assert(src_pos.count() == src_nrm.count());

	matx4 inv;

	if (!inv.invert(mat))
		return false;

	pos.realloc(src_pos.count());
	nrm.realloc(src_nrm.count());

	bbox.setMinimum(vect3(  FLT_MAX,  FLT_MAX,  FLT_MAX  ));
	bbox.setMaximum(vect3( -FLT_MAX, -FLT_MAX, -FLT_MAX  ));

	for (unsigned i = 0; i < pos.count(); ++i)
	{
		vect3& pos_i = vect3::cast(pos.get(i));
		vect3& nrm_i = vect3::cast(nrm.get(i));

		const vect3& src_pos_i = vect3::cast(src_pos.get(i));
		const vect3& src_nrm_i = vect3::cast(src_nrm.get(i));

		mat.transform3(src_pos_i, pos_i);
		inv.transformTS< 3 >(src_nrm_i, nrm_i);	// for normals use the transposed inverse of the baking matrix,
		nrm_i.normalise();						// renormalise

		bbox.merge(pos_i);
	}

	return true;
}


template < unsigned NUM_PLANES_T >
bool copyAttrPNTransformClipped(
	Box3& bbox,
	VectorArray< float, 3 >& pos,
	VectorArray< float, 3 >& nrm,
	const VectorArrayImm< float, 3 >& src_pos,
	const VectorArrayImm< float, 3 >& src_nrm,
	const matx4& mat,
	const Clipper< NUM_PLANES_T >& clipper)
{
	assert(src_pos.count());
	assert(src_pos.count() == src_nrm.count());

	matx4 inv;

	if (!inv.invert(mat))
		return false;

	pos.realloc(src_pos.count());
	nrm.realloc(src_nrm.count());

	bbox.setMinimum(vect3(  FLT_MAX,  FLT_MAX,  FLT_MAX  ));
	bbox.setMaximum(vect3( -FLT_MAX, -FLT_MAX, -FLT_MAX  ));

	for (unsigned i = 0; i < pos.count(); ++i)
	{
		vect3& pos_i = vect3::cast(pos.get(i));
		vect3& nrm_i = vect3::cast(nrm.get(i));

		const vect3& src_pos_i = vect3::cast(src_pos.get(i));
		const vect3& src_nrm_i = vect3::cast(src_nrm.get(i));

		if (clipper.isPointInside(src_pos_i))
		{
			mat.transform3(src_pos_i, pos_i);
			inv.transformTS< 3 >(src_nrm_i, nrm_i);	// for normals use the transposed inverse of the baking matrix,
			nrm_i.normalise();						// renormalise
		}
		else
		{
			pos_i = src_pos_i;
			nrm_i = src_nrm_i;
		}

		bbox.merge(pos_i);
	}

	return true;
}


template < unsigned ATTR_CAPACITY_T, typename INDEX_T, unsigned NUM_VERTS_T >
void completeFrom(
	const typename AttrBuffer< ATTR_CAPACITY_T >::template AttrAccessor< float, 3 >& nrm,
	const typename AttrBuffer< ATTR_CAPACITY_T >::template AttrAccessorImm< float, 3 >& pos,
	const VectorArrayImm< INDEX_T, NUM_VERTS_T >& tri)
{
	assert(pos.count());
	assert(pos.count() == nrm.count());
	assert(tri.count());

	setFloatPrecision(PRECISION_SINGLE);

	for (unsigned i = 0; i < nrm.count(); ++i)
		vect3::cast(nrm.get(i)) = vect3(0.f, 0.f, 0.f);

	for (unsigned i = 0; i < tri.count(); ++i)
	{
		vect3 n_tri(0.f, 0.f, 0.f);

		for (unsigned j = 0; j < NUM_VERTS_T; ++j)
		{
			vect3 v[2], n;

			v[0].sub(
				vect3::cast(pos.get(tri.get(i)[(j + 1) % NUM_VERTS_T])),
				vect3::cast(pos.get(tri.get(i)[j])));

			v[1].sub(
				vect3::cast(pos.get(tri.get(i)[(j + 2) % NUM_VERTS_T])),
				vect3::cast(pos.get(tri.get(i)[j])));

			n.cross(v[0], v[1]);

			const float epsilon = 1e-11;

			if (!(n.sqr() > epsilon))
				break;

			n.normalise();
			n_tri.add(n);
		}

		if (n_tri == vect3(0.f, 0.f, 0.f))
			continue;

		n_tri.normalise();

		const float epsilon = 1e-6;
		const float error = fabsf(n_tri.norm() - 1.f);

		assert(error < epsilon);

		for (unsigned j = 0; j < NUM_VERTS_T; ++j)
			vect3::cast(nrm.get(tri.get(i)[j])).add(n_tri);
	}

	for (unsigned i = 0; i < nrm.count(); ++i)
	{
		vect3& n = vect3::cast(nrm.get(i));

		if (n != vect3(0.f, 0.f, 0.f))
		{
			n.normalise();

			const float epsilon = 1e-6;
			const float error = fabsf(n.norm() - 1.f);

			assert(error < epsilon);
		}
	}
}

} // namespace rend

#endif // __mesh_utils_H__
